using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Reflection;

using Microsoft.VisualStudio.TestTools.UnitTesting;
using Pegasus.Reflection;

namespace Pegasus.UnitTests.Reflections
{
	/// <summary>
	/// Tests the TypeName class
	/// </summary>
	[TestClass]
	public class TypeNameTests : PegasusUnitTest
	{
		// Local Instance Values
		private Type[] m_standardTypes = 
		{
			// Standard types
			typeof( object ),
			typeof( bool ),
			typeof( byte ),
			typeof( sbyte ),
			typeof( char ),
			typeof( short ),
			typeof( ushort ),
			typeof( int ),
			typeof( uint ),
			typeof( long ),
			typeof( ulong ),
			typeof( float ),
			typeof( double ),
			typeof( decimal ),
			typeof( string ),
			typeof( DateTime ),
			typeof( TimeSpan ),
			typeof( Guid ),
			typeof( IntPtr ),
			typeof( UIntPtr ),

			// Arrays of standard types
			typeof( object[] ),
			typeof( bool[] ),
			typeof( byte[] ),
			typeof( sbyte[] ),
			typeof( char[] ),
			typeof( short[] ),
			typeof( ushort[] ),
			typeof( int[] ),
			typeof( uint[] ),
			typeof( long[] ),
			typeof( ulong[] ),
			typeof( float[] ),
			typeof( double[] ),
			typeof( decimal[] ),
			typeof( string[] ),
			typeof( DateTime[] ),
			typeof( TimeSpan[] ),
			typeof( Guid[] ),
			typeof( IntPtr[] ),
			typeof( UIntPtr[] ),
		

			// Generic Types
			typeof( List<byte> ),
			typeof( List<char> ),
			typeof( List<int> ),
			typeof( List<int> ),
			typeof( List<string> ),
			typeof( List<string[]> ),
			typeof( List<string>[] ),
			typeof( List<string[]>[] ),

			typeof( Dictionary<int,byte> ),
			typeof( Dictionary<int,string> ),
			typeof( Dictionary<int,string[]> ),

			typeof( SortedDictionary<int,string> ),
			typeof( SortedDictionary<int,string>[] ),
			typeof( SortedDictionary<string,ICustomFormatter>[] ),
	
			typeof( SortedDictionary<string,List<int>> ),
			typeof( SortedDictionary<string,List<int>>[] ),
			typeof( SortedDictionary<string,List<int[]>>[] ),
			typeof( SortedDictionary<string,List<Dictionary<DateTime,ICustomFormatter>>> ),
			typeof( SortedDictionary<string,List<Dictionary<DateTime,ICustomFormatter>>>[] ),
			typeof( SortedDictionary<string,List<Dictionary<DateTime,ICustomFormatter[]>[]>[]>[] ),

			typeof( SortedDictionary<string,string>.KeyCollection ),
			typeof( SortedDictionary<string,string>.KeyCollection[] ),
		};

		/// <summary>
		/// Initializes a new instance of the <see cref="TypeNameTests"/> class.
		/// </summary>
		public TypeNameTests()
		{
		}

		/// <summary>
		/// Test the TypeName class on standard types
		/// </summary>
		[TestMethod]
		public void TypeNameStandardTest()
		{
			TestTheListOfTypes( m_standardTypes );
		}

		/// <summary>
		/// Test the TypeName class on all the types in the .NET Core Library Assembly.
		/// </summary>
//		[TestMethod]
// lexnn: This test shows a bug that needs to be fixed in the TypeName Object.
// I think that I need to rethink the type name parsing algorithm.
		public void TypeNameCoreLibraryTest()
		{
			// .NET Core library Types
			Type[] types = typeof( object ).Assembly.GetTypes();
			TestTheListOfTypes( types );
		}


		/// <summary>
		/// Test the TypeName class on Nested Generic types
		/// </summary>
//		[TestMethod]
// lexnn: same as above.
		public void TypeNameGenericNestedTest()
		{
			Type testType = typeof( GenericNestedOutter<int, string>.GenericNestedInner<int, string> );
			TypeName typeName = new TypeName( testType.AssemblyQualifiedName );
			TestIfTypeNameMatchesType( testType, typeName );

			typeName = new TypeName( testType.Assembly.FullName, testType.FullName );
			TestIfTypeNameMatchesType( testType, typeName );
		}

		/// <summary>
		/// Tests the list of types.
		/// </summary>
		/// <param name="typesArray">The types array.</param>
		private void TestTheListOfTypes( Type[] typesArray )
		{
			foreach( Type testType in typesArray )
			{
				try
				{
					TypeName typeName = new TypeName( testType.AssemblyQualifiedName );
					TestIfTypeNameMatchesType( testType, typeName );

					typeName = new TypeName( testType.Assembly.FullName, testType.FullName );
					TestIfTypeNameMatchesType( testType, typeName );
				}
				catch( Exception e )
				{
					throw new Exception( string.Format( "Error checking type {0}", testType.ToString() ), e );
				}
			}
		}

		/// <summary>
		/// Tests the type of if type name matches.
		/// </summary>
		/// <param name="testType">Type of the test.</param>
		/// <param name="typeName">Name of the type.</param>
		private void TestIfTypeNameMatchesType( Type testType, TypeName typeName )
		{
			Assert.AreEqual<string>( testType.FullName, typeName.FullTypeName, TypeMessage( testType ) );
			Assert.AreEqual<string>( testType.Assembly.FullName, typeName.FullyQualifiedAssemblyName, TypeMessage( testType ) );

			Assert.IsTrue( testType.IsArray == typeName.IsArray, TypeMessage( testType ) );
			Assert.IsTrue( testType.IsGenericType == typeName.IsGenericType, TypeMessage( testType ) );

			if( testType.IsGenericType )
			{
				Type[] genericArgs = testType.GetGenericArguments();
				ReadOnlyCollection<TypeName> genericTypeNames = typeName.GenericParameters;

				int count = genericArgs.Length;
				Assert.IsTrue( count == genericTypeNames.Count, TypeMessage( testType ) );

				for( int x = 0; x < count; x++ )
				{
					TestIfTypeNameMatchesType( genericArgs[ x ], genericTypeNames[ x ] );
				}
			}
		}

		/// <summary>
		/// Types the message.
		/// </summary>
		/// <param name="testType">Type of the test.</param>
		/// <returns></returns>
		private string TypeMessage( Type testType )
		{
			return string.Format( "Parsing type {0}", testType.FullName );
		}
	}

	/// <summary>
	/// This class is used to test nested generic classes.
	/// </summary>
	/// <typeparam name="TKey"></typeparam>
	/// <typeparam name="TValue"></typeparam>
	public class GenericNestedOutter<TKey, TValue>
	{
		public class GenericNestedInner<TKey2, TValue2>
		{
		}
	}
}
